
#include <functional>
#include <iostream>

#include <conio.h>

#include "glare.core.GpuResourceFactory.h"
#include "glare.core.RenderWindow.h"
#include "glare.core.DrawContext.h"

#include "glare.d3d11.Driver.h"
#include "glare.gles2.Driver.h"

#include "glare.core.Win32Window.h"
#include "glare.core.ShaderFactory.h"
#include "glare.core.Shader.h"
#include "glare.core.ShaderInputDeclaration.h"
#include "Glare.Core.GpuBufferDescription.h"
#include "Glare.Core.GpuBuffer.h"

using namespace Glare;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

	MSG msg = {0};
	try
	{
		// create a platform window
		Win32WindowConstructionParams* w32_params = new Win32WindowConstructionParams();
		w32_params->InstanceHandle = (uint)hInstance;
		w32_params->Class = "glare_window_class";
		w32_params->Title = "Glare3D - Dev.Playpen";

		PlatformWindow* platform_window = PlatformWindow::CreateDefaultWindow(800, 600, w32_params);

		// start the driver
		//Driver* driver = new D3D11Driver();
		Driver* driver = new GLES2Driver();

		// create a render window
		RenderWindow* render_window = driver->CreateRenderWindow(platform_window);
		render_window->Initialize();

		// get the immediate draw context
		auto immediate_context = driver->GetImmediateDrawContext();

		// define a rendering viewport
		Viewport viewport;
		viewport.Left = 0;
		viewport.Top = 0;
		viewport.Width = (float)render_window->GetCanvasWidth();
		viewport.Height = (float)render_window->GetCanvasHeight();
		viewport.MinDepth = 0;
		viewport.MaxDepth = 1;

		// get the Shader / InputDeclaration factory
		auto shader_factory = driver->GetShaderFactory();
		auto vertex_shader = shader_factory->CreateVertexShader();
		auto pixel_shader = shader_factory->CreatePixelShader();

		string errors;
		// load the vertex & pixel shader
#if GLARE_PLATFORM == GLARE_PLATFORM_WIN32
		if (driver->GetName() == D3D11Driver::Name)
		{
			vertex_shader->Compile("float4 main(float4 position: POSITION) : SV_POSITION { return position; }", &errors);

			if (errors.length() > 0)
				driver->WriteLog("error while compiling vertex shader: " + errors, LMT_Error);

			pixel_shader->Compile("float4 main(float4 Pos : SV_POSITION) : SV_Target { return float4(0,1,0,1); }", &errors);

			if (errors.length() > 0)
				driver->WriteLog("error while compiling pixel shader: " + errors, LMT_Error);
		}
#endif
		if (driver->GetName() == GLES2Driver::Name)
		{
			vertex_shader->Compile("attribute vec4 POSITION; void main(void) { gl_Position = POSITION; }", &errors);

			if (errors.length() > 0)
				driver->WriteLog("error while compiling vertex shader: " + errors, LMT_Error);

			pixel_shader->Compile("void main(void) { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); }", &errors);

			if (errors.length() > 0)
				driver->WriteLog("error while compiling pixel shader: " + errors, LMT_Error);
		}

		// create an input declaration for the vertex shader
		auto input_decl = shader_factory->CreateInputDeclaration();

		// add the elements to the input declaration
		ShaderInputElement elem;
		elem.Format = PF_R32G32B32_FLOAT;
		elem.SemanticName = "POSITION";
		input_decl->InputElements.push_back(elem);

		// build the input declaration for the vertex shader
		input_decl->Build(vertex_shader);

		auto resource_factory = driver->GetGpuResourceFactory();

		GpuBufferDescription desc;
		desc.BindFlags = GpuResourceBind::VertexBuffer;
		desc.UsageFlags = GpuResourceUsage::Default;
		desc.CpuAccessFlags = GpuResourceCpuAccess::None;
		desc.MiscFlags = 0;
		desc.ElementSize = sizeof(Vector3);
		desc.ElementCount = 3;

		// create the vertex buffer
		auto vertex_buffer = resource_factory->CreateBuffer(desc);

		// create a binding for the vertex buffer
		GpuVertexBufferBinding binding;
		binding.Buffer = vertex_buffer;
		binding.Stride = sizeof(Vector3);
		binding.Offset = 0;

		Box dest_box;
		dest_box.right = sizeof(Vector3) * 3;

		Vector3 vertices[3] = 
		{
			Vector3(-1, -1, 0),
			Vector3(0, 1, 0),
			Vector3(1, -1, 0)
		};

		// copy the data to the vertex buffer
		immediate_context->UpdateGpuResource(vertex_buffer, 0, &dest_box, vertices, sizeof(Vector3), 0);

		// Main message loop
		while (msg.message != WM_QUIT)
		{
			if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else
			{
				// set the RenderTarget, the Viewport and perform a clearing operation
				immediate_context->OM_SetRenderTarget(render_window, NULL);
				immediate_context->RS_SetViewport(viewport);
				immediate_context->ClearRenderTarget(render_window, ColourValue::Red);

				// activate the vertex buffer
				immediate_context->IA_SetVertexBuffer(binding);

				// set the vertex & pixel shader
				immediate_context->VS_SetVertexShader(vertex_shader);
				immediate_context->PS_SetPixelShader(pixel_shader);

				// set the primitive type & input declaration for the draw call
				immediate_context->IA_SetPrimitiveType(GpuPrimitive::TriangleList);
				immediate_context->IA_SetInputDeclaration(input_decl);

				// submit the draw call
				immediate_context->Draw(3);

				// swap the GPU buffers
				render_window->Present();
			}
		}

		delete vertex_buffer;
		delete input_decl;

		delete pixel_shader;
		delete vertex_shader;

		delete render_window;
		delete platform_window;
		delete driver;
	}
	catch (Exception e)
	{
		MessageBox(0, e.what(), 0, 0);
	}

	return (int)msg.wParam;
}
